//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License
// along with this program.  If not, see http://www.gnu.org/licenses/.
// 

#include "cpu.h"

Define_Module(cpu);

void cpu::initialize(){
    this->timeJob = 0;
    this->currentComand = NULL;
    this->queueComands.setName("queueComand");
    this->queueReqComands.setName("queueReqComand");

    this->responseTimeStadistic = new cStdDev("responseTime");
    this->cpuUsedTime = 0;
    this->queueMeanSize = 0;
    this->currentTime = 0;
    this->prevTime = 0;

    //for Round Robin planification
    this->quantum = par("quantum");



    finishSimulation = new cADByStddev();
    finishSimulation->setParameters(0.01,90000);
    finishSimulation->start();
}

void cpu::handleMessage(cMessage *msg){

//  FINALIZATION THE SIMULATION ACCURACY 0.05 AND REPS 1000
    if (finishSimulation->detected())   endSimulation();

//------------------------------------------------------------------------------------------------------------------------
    //ARRIVE COMMAND FROM TERMINAL, CPU MUST CALCULATE THE TIME TO PROCESS THE REQUIREMENTS
    //AND SCHEDUL WITCH COMMAND IS GONNA BE PROCESS
    if(strcmp(msg->getName(),"Comand")==0){
        comand = (Comand*) msg;
        if(comand->getQueueJobsArraySize()<=0){
            comand->setQueueJobsArraySize((unsigned) (comand->getNroRequirements()+1));
            double job = (par("timeProcess").doubleValue()/(comand->getNroRequirements()+1));//CONTROLAR 0.48 COMO PARAMETRO DEL SISTEMA
            for (int i = 0; i < (int) comand->getQueueJobsArraySize(); i++){
                comand->setQueueJobs(i, job);
            }
        }
        if(currentComand == NULL and this->queueComands.isEmpty()){
            currentComand = comand;
        }else{
            queueComands.insert(comand);
            comand->setQueueInitTime(simTime());
            processQueueMeanSize();
        }
    }
//-------------------------------------------------------------------------------------------------------------------------
    //ARRIVE MESSAGE FROM CPU (IT SELF) TO WARN THAT THE CPU'S JOB FINISH AND IF IT HAS REQUIREMENTS
    //CPU MUST TO SEND A MESSAGE TO ANY DISC
    else if(strcmp(msg->getName(),"finishedJob") == 0){
        if(this->currentComand->getNroRequirements()>0){
            currentRequirement = new Requirement("Requirement");
            currentRequirement->setIdComand(currentComand->getIdComand());
            currentRequirement->setNroDisc(intuniform(1,2,0));
            currentRequirement->setTimeIn(simTime());
            currentComand->setNroRequirements(currentComand->getNroRequirements()-1);
            queueReqComands.insert(currentComand);
            currentComand = NULL;
            par("cpuIsFree").setBoolValue(true);
            // chose randomly whitch disc attents the requeriment
            if(currentRequirement->getNroDisc()==1) send(currentRequirement, "out_disc"); else send(currentRequirement, "out_disc1");
        }
        delete msg;
    }
//------------------------------------------------------------------------------------------------------------------------
    //ARRIVE MESSAGE FROM CPU (IT SELF) TO WARN THAT THE CPU HAS THE LAST COMMAND'S JOB
    //AND THE COMMAND GETS OUT FROM THE SISTEM
    else if(strcmp(msg->getName(),"finalJob")==0){
        currentComand->setTimeOut(simTime());
        simtime_t responseMeanTime = (currentComand->getTimeOut() - currentComand->getTimeIn());
        responseTimeStadistic->collect(responseMeanTime);
        finishSimulation->collect(responseMeanTime.dbl());
        delete(currentComand);
        currentComand = NULL;
        par("cpuIsFree").setBoolValue(true);
        bubble("COMAND GOT OUT");
        //fprintf(cpuUsedFIFO,"%f\n",timeJob);
        delete msg;
    }
//-----------------------------------------------------------------------------------------------------------------------
    //ARRIVE MESSAGE FROM DISC TO WARM THE REQUIREMENT IS FINISHED AND THE CPU MUST INSERT THE COMMAND
    //IN THE QUEUE COMAND (BEFORE THAT THE COMMAND IS IN AN AUXILIAR QUEUE CALLED queueRecComand)
    else if(strcmp(msg->getName(),"finishedRequirement") == 0){
        Requirement *requirementFinished = (Requirement*)msg;
        bool found = false;
        for(int i=0;i<queueReqComands.getLength() and !found;i++){
            if(((Comand*)queueReqComands.get(i))->getIdComand() == requirementFinished->getIdComand()){
                Comand *comand = (Comand*)queueReqComands.get(i);
                queueReqComands.remove(comand);
                queueComands.insert(comand);
                comand->setQueueInitTime(simTime());
                processQueueMeanSize();
                found = true;
            }
        }
        if(!found)bubble("ERRORRRRR");
    //WARN AN COMMAND WAS INTERRUPTED BY EXCEED THE QUANTUM
    }else if(strcmp(msg->getName(),"interruption") == 0)
        bubble("INTERRUPTION");

    //HERE THE CPU PROCESS THE COMMAND'S JOB, RESET THE COMMAND'S JOBS QUEUE AND SEND IT SELF A MESSAGE
    if((par("cpuIsFree").boolValue()==true) and (!queueComands.isEmpty() or currentComand != NULL)){
        if(currentComand == NULL){
            if(par("priorityLess").boolValue()==true)
                currentComand = searchCommandJob();
            else
                if(par("planHigher").boolValue()==true)
                    currentComand = searchCommandPriority();
                else
                    currentComand = (Comand*) queueComands.pop();
            processQueueMeanSize();
        }
        if(currentComand != NULL){
            if(par("roundRobin").boolValue() == true){
                if(currentComand->getQueueJobsArraySize() >0){
                    if(currentComand->getQueueJobs(0) > quantum){
                        par("cpuIsFree").setBoolValue(false);
                        cpuUsedTime = cpuUsedTime + quantum;
                        simtime_t leftTime = currentComand->getQueueJobs(0) - quantum;
                        currentComand->setQueueJobs(0,leftTime);
                        this->queueComands.insert(currentComand);
                        processQueueMeanSize();
                        par("cpuIsFree").setBoolValue(true);
                        currentComand = NULL;
                        cMessage *interrup = new cMessage("interruption");
                        scheduleAt(simTime()+quantum,interrup);
                    }else
                        processComand();
                }
            }else
                processComand();
        }
    }
}

void cpu::copyComand(Comand *comand){
    Comand *cpuComand = new Comand("Comand");
    cpuComand->setIdComand(comand->getIdComand());
    cpuComand->setNroRequirements(comand->getNroRequirements());
    cpuComand->setTimeProcess(comand->getTimeProcess());
    for(int i=0;i < (int)comand->getQueueJobsArraySize();i++)
        cpuComand->setQueueJobs(i,comand->getQueueJobs(i));
}

void cpu::processComand(){
    if(currentComand->getQueueJobsArraySize() >0){
       par("cpuIsFree").setBoolValue(false);
       cpuUsedTime = cpuUsedTime + currentComand->getQueueJobs(0);
       timeJob = simTime() + currentComand->getQueueJobs(0);
       cMessage *finishedJob;
       int i;
       if (currentComand->getQueueJobsArraySize() >1){
           for(i=0;i < (int)currentComand->getQueueJobsArraySize()-1;i++)
               currentComand->setQueueJobs(i,currentComand->getQueueJobs(i+1));
           finishedJob = new cMessage("finishedJob");//show that the CPU job is finished.
       }else
           finishedJob = new cMessage("finalJob");
       i = currentComand->getQueueJobsArraySize()-1;
       currentComand->setQueueJobsArraySize(i);
       finishedJob->setKind(currentComand->getIdComand());
       scheduleAt(timeJob, finishedJob);
   }
}

void cpu::processQueueMeanSize(){
    double auxCurrentTime = simTime().dbl();
    currentTime = auxCurrentTime - prevTime;
    queueMeanSize = queueMeanSize + (currentTime*queueComands.length());
    prevTime = auxCurrentTime;
}

Comand* cpu::searchCommandJob(){
    Comand* choosenComand;
    if(queueComands.length()>1){
        choosenComand = (Comand*)queueComands.get(0); ;
        for(int i=1;i<queueComands.getLength();i++){
            if(choosenComand->getQueueJobs(0) >= ((Comand*)queueComands.get(i))->getQueueJobs(0)){
                choosenComand = (Comand*)queueComands.get(i);
            }
        }
    }else
        choosenComand = (Comand*)queueComands.get(0);
    queueComands.remove(choosenComand);
    return choosenComand;
}

Comand* cpu::searchCommandPriority(){
    Comand* choosenComand;
    if(queueComands.length()>1){
        choosenComand = (Comand*)queueComands.get(0);
        for(int i=1;i<queueComands.getLength();i++){
            if(priorityCommand(choosenComand) <= priorityCommand((Comand*)queueComands.get(i))){
                choosenComand = (Comand*)queueComands.get(i);
            }
        }
    }else
        choosenComand = (Comand*)queueComands.get(0);
    queueComands.remove(choosenComand);
    return choosenComand;
}

double cpu::priorityCommand(Comand *comand){
    simtime_t priority;
    simtime_t waitTime;
    simtime_t serviceTime;
    waitTime = simTime() - comand->getQueueInitTime();
    serviceTime = comand->getQueueJobs(0);
    priority = (waitTime + serviceTime)/serviceTime;
    return priority.dbl();
}

void cpu::finish(){
    ev<<"*********************************"<<endl;
    ev<<"CPU"<<endl;
    ev<<"Rho:"<<this->cpuUsedTime/simTime()<<endl;
    ev<<"L:"<<this->queueMeanSize/simTime()<<endl;
    ev<<"W:"<<this->responseTimeStadistic->getMean()<<endl;
    //record scalar  result in a file
    recordScalar("simulated time",simTime());
    recordScalar("Cpu Used",cpuUsedTime/simTime());
    recordScalar("Queue Size",queueMeanSize/simTime());
    recordScalar("Response Time",responseTimeStadistic->getMean());

}
